package com.daniulive.smartpublisher;


import android.app.Activity;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;

import com.eventhandle.NTSmartEventCallbackV2;
import com.eventhandle.NTSmartEventID;
import com.voiceengine.NTAudioRecordV2;
import com.voiceengine.NTAudioRecordV2Callback;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.List;

public class PublishUtil {
    private static PublishUtil publishUtil;

    public static PublishUtil getInstance(Context context) {
        if (publishUtil == null) publishUtil = new PublishUtil(context);
        return publishUtil;
    }

    private static String TAG = "##########";
    private SmartPublisherJniV2 libPublisher = null;
    NTAudioRecordV2 audioRecord_ = null;
    NTAudioRecordV2Callback audioRecordCallback_ = null;

    private Context myContext;//上下文context
    private long publisherHandle = 0;//推送handle

    /* 推送类型选择
     * 0: 音视频
     * 1: 纯音频
     * 2: 纯视频
     * */
    private int pushType = 0;

    /* 推流分辨率选择
     * 0: 320*240
     * 4: 640*480
     * 2: 720*480
     * 3: 1280*720
     * */

    /* video软编码profile设置
     * 1: baseline profile
     * 2: main profile
     * 3: high profile
     * */
    private int sw_video_encoder_profile = 1;    //default with baseline profile
    /**
     * video软编码speed设置
     * 6
     * 5
     * 4
     * 3 默认
     * 2
     * 1
     */
    private int sw_video_encoder_speed = 3;

    /* 推送类型选择
     * 0: 视频软编码(H.264)
     * 1: 视频硬编码(H.264)
     * 2: 视频硬编码(H.265)
     * */
    private int videoEncodeType = 0;


    private boolean isPushingRtmp = false;    //RTMP推送状态
    //  RTMP推送URL"rtmp://192.168.0.4:1935/live/home";
    private String publishURL = "";


    private int videoWidth = 1280;
    private int videoHeight = 720;

    private boolean is_noise_suppression = true;
    private boolean is_agc = false;
    private boolean is_speex = false;

    private boolean is_sw_vbr_mode = true;

    private String encrypt_key = "";
    private String encrypt_iv = "";

    private static final int PUBLISHER_EVENT_MSG = 1;
    private static final int PUBLISHER_USER_DATA_MSG = 2;

    static {
        System.loadLibrary("SmartPublisher");
    }


    public PublishUtil(Context context) {
        myContext = context;
        Log.i(TAG, "onCreate..");

        libPublisher = new SmartPublisherJniV2();
        libPublisher.InitRtspServer(myContext);      //和UnInitRtspServer配对使用，即便是启动多个RTSP服务，也只需调用一次InitRtspServer，请确保在OpenRtspServer之前调用
    }

    public void switchResolution(int w, int h) {
        videoWidth = w;
        videoHeight = h;
    }

    public void onDestroy() {
        if (isPushingRtmp) {
            if (audioRecord_ != null) {
                Log.i(TAG, "surfaceDestroyed, call StopRecording..");

                audioRecord_.Stop();

                if (audioRecordCallback_ != null) {
                    audioRecord_.RemoveCallback(audioRecordCallback_);
                    audioRecordCallback_ = null;
                }

                audioRecord_ = null;
            }

            stopPush();
            isPushingRtmp = false;
            if (publisherHandle != 0) {
                if (libPublisher != null) {
                    libPublisher.SmartPublisherClose(publisherHandle);
                    publisherHandle = 0;
                }
            }
            libPublisher.UnInitRtspServer();      //如已启用内置服务功能(InitRtspServer)，调用UnInitRtspServer, 注意，即便是启动多个RTSP服务，也只需调用UnInitRtspServer一次
        }
    }

    //停止rtmp推送
    public void stopPush() {
        if (!isPushingRtmp) {
            return;
        }
        isPushingRtmp = false;
        if (audioRecord_ != null) {
            Log.i(TAG, "stopPush, call audioRecord_.StopRecording..");

            audioRecord_.Stop();

            if (audioRecordCallback_ != null) {
                audioRecord_.RemoveCallback(audioRecordCallback_);
                audioRecordCallback_ = null;
            }

            audioRecord_ = null;
        }
        if (libPublisher != null) {
            libPublisher.SmartPublisherStopPublisher(publisherHandle);
        }

        if (publisherHandle != 0) {
            if (libPublisher != null) {
                libPublisher.SmartPublisherClose(publisherHandle);
                publisherHandle = 0;
            }
        }
    }


    public void startPush() {
        Log.i(TAG, "推流URL:" + publishURL);
        if (isPushingRtmp) {
            stopPush();
            isPushingRtmp = false;
            return;
        }

        if (libPublisher == null)
            return;
        InitAndSetConfig();

        if (libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0) {
            Log.e(TAG, "Failed to set publish stream URL..");
        }

        if (encrypt_key != null && !encrypt_key.isEmpty()) {
            Log.i(TAG, "encrypt_key:" + encrypt_key);

            int is_encrypt_video = 1;
            int is_encrypt_audio = 1;

            if (pushType == 1) {
                is_encrypt_video = 0;
            } else if (pushType == 2) {
                is_encrypt_audio = 0;
            }

            libPublisher.SetRtmpEncryptionOption(publisherHandle, publishURL, is_encrypt_video, is_encrypt_audio);

            //加密算法可自行设置
            int encryption_algorithm = 1;
            libPublisher.SetRtmpEncryptionAlgorithm(publisherHandle, publishURL, encryption_algorithm);

            int key_len = 16;

            if (encrypt_key.length() > 16 && encrypt_key.length() <= 24) {
                key_len = 24;
            } else if (encrypt_key.length() > 24) {
                key_len = 32;
            }

            byte[] key = new byte[key_len];

            for (int i = 0; i < key_len; i++) {
                key[i] = 0;
            }

            try {
                byte[] key_utf8 = encrypt_key.getBytes("UTF-8");

                int copy_len = key_utf8.length < key_len ? key_utf8.length : key_len;

                for (int i = 0; i < copy_len; ++i) {
                    key[i] = key_utf8[i];
                }

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            int ret = libPublisher.SetRtmpEncryptionKey(publisherHandle, publishURL, key, key.length);

            if (ret != 0) {
                Log.e(TAG, "Call SmartPublisherSetRtmpEncryptionKey failed, errorID: " + ret);
            }
        }

        if (encrypt_iv != null && !encrypt_iv.isEmpty()) {
            int iv_len = 16;

            byte[] iv = new byte[iv_len];

            for (int i = 0; i < iv_len; i++) {
                iv[i] = 0;
            }

            try {
                byte[] iv_utf8 = encrypt_iv.getBytes("UTF-8");

                int copy_len = iv_utf8.length < iv_len ? iv_utf8.length : iv_len;

                for (int i = 0; i < copy_len; ++i) {
                    iv[i] = iv_utf8[i];
                }

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            int ret = libPublisher.SetRtmpEncryptionIV(publisherHandle, publishURL, iv, iv.length);

            if (ret != 0) {
                Log.e(TAG, "Call SetRtmpEncryptionIV failed, errorID: " + ret);
            }
        }

        int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
        if (startRet != 0) {
            isPushingRtmp = false;
            Log.e(TAG, "Failed to start push stream..");
            return;
        }
        if (pushType == 0 || pushType == 1) {
            CheckInitAudioRecorder();    //enable pure video publisher..
        }
        isPushingRtmp = true;
    }

    public boolean isPushingRtmp() {
        return isPushingRtmp;
    }

    public int getVideoWidth() {
        return videoWidth;
    }

    public int getVideoHeight() {
        return videoHeight;
    }

    public void SmartPublisherOnCaptureVideoData(byte[] data, int currentCameraType, int currentOrigentation) {
        libPublisher.SmartPublisherOnCaptureVideoData(publisherHandle, data, data.length, currentCameraType, currentOrigentation);
    }

    public void SetCameraFPS(Camera.Parameters parameters) {
        if (parameters == null)
            return;

        int[] findRange = null;

        int defFPS = 20 * 1000;

        List<int[]> fpsList = parameters.getSupportedPreviewFpsRange();
        if (fpsList != null && fpsList.size() > 0) {
            for (int i = 0; i < fpsList.size(); ++i) {
                int[] range = fpsList.get(i);
                if (range != null
                        && Camera.Parameters.PREVIEW_FPS_MIN_INDEX < range.length
                        && Camera.Parameters.PREVIEW_FPS_MAX_INDEX < range.length) {
                    Log.i(TAG, "Camera index:" + i + " support min fps:" + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]);

                    Log.i(TAG, "Camera index:" + i + " support max fps:" + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);

                    if (findRange == null) {
                        if (defFPS <= range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
                            findRange = range;

                            Log.i(TAG, "Camera found appropriate fps, min fps:" + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]
                                    + " ,max fps:" + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
                        }
                    }
                }
            }
        }

        if (findRange != null) {
            parameters.setPreviewFpsRange(findRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], findRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
        }
    }

    public void setPublishUrl(String url) {
        this.publishURL = url;
    }

    class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback {
        @Override
        public void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number) {
            if (publisherHandle != 0) {
                libPublisher.SmartPublisherOnPCMData(publisherHandle, data, size, sampleRate, channel, per_channel_sample_number);
            }
        }
    }

    void CheckInitAudioRecorder() {
        if (audioRecord_ == null) {
            //audioRecord_ = new NTAudioRecord(this, 1);

            audioRecord_ = new NTAudioRecordV2(myContext);
        }

        if (audioRecord_ != null) {
            Log.i(TAG, "CheckInitAudioRecorder call audioRecord_.start()+++...");

            audioRecordCallback_ = new NTAudioRecordV2CallbackImpl();

            // audioRecord_.IsMicSource(true);      //如采集音频声音过小，可以打开此选项

            // audioRecord_.IsRemoteSubmixSource(true);

            audioRecord_.AddCallback(audioRecordCallback_);

            audioRecord_.Start();

            Log.i(TAG, "CheckInitAudioRecorder call audioRecord_.start()---...");


            //Log.i(TAG, "onCreate, call executeAudioRecordMethod..");
            // auido_ret: 0 ok, other failed
            //int auido_ret= audioRecord_.executeAudioRecordMethod();
            //Log.i(TAG, "onCreate, call executeAudioRecordMethod.. auido_ret=" + auido_ret);
        }
    }


    class EventHandeV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {
            String publisher_event = "";
            switch (id) {
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:
                    publisher_event = "开始..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
                    publisher_event = "连接中..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
                    publisher_event = "连接失败..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
                    publisher_event = "连接成功..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
                    publisher_event = "连接断开..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
                    publisher_event = "关闭..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
                    publisher_event = "开始一个新的录像文件 : " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
                    publisher_event = "已生成一个录像文件 : " + param3;
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
                    publisher_event = "发送时延: " + param1 + " 帧数:" + param2;
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
                    publisher_event = "快照: " + param1 + " 路径：" + param3;

                    if (param1 == 0) {
                        publisher_event = publisher_event + "截取快照成功..";
                    } else {
                        publisher_event = publisher_event + "截取快照失败..";
                    }
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
                    publisher_event = "RTSP服务URL: " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:
                    publisher_event = "RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:
                    publisher_event = "服务器不支持RTSP推送, 推送的RTSP URL: " + param3;
                    break;
            }

            String str = "当前回调状态：" + publisher_event;

            Log.i(TAG, str);
        }
    }


    private void InitAndSetConfig() {
        Log.i(TAG, "videoWidth: " + videoWidth + " videoHeight: " + videoHeight + " pushType:" + pushType);

        int audio_opt = 1;
        int video_opt = 1;

        if (pushType == 1) {
            video_opt = 0;
        } else if (pushType == 2) {
            audio_opt = 0;
        }

        publisherHandle = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt, videoWidth, videoHeight);

        if (publisherHandle == 0) {
            Log.e(TAG, "sdk open failed!");
            return;
        }

        Log.i(TAG, "publisherHandle=" + publisherHandle);

        if (videoEncodeType == 1) {
            int h264HWKbps = setHardwareEncoderKbps(true, videoWidth, videoHeight);

            Log.i(TAG, "h264HWKbps: " + h264HWKbps);

            int isSupportH264HWEncoder = libPublisher
                    .SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);

            if (isSupportH264HWEncoder == 0) {
                Log.i(TAG, "Great, it supports h.264 hardware encoder!");
            }
        } else if (videoEncodeType == 2) {
            int hevcHWKbps = setHardwareEncoderKbps(false, videoWidth, videoHeight);

            Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);

            int isSupportHevcHWEncoder = libPublisher
                    .SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);

            if (isSupportHevcHWEncoder == 0) {
                Log.i(TAG, "Great, it supports hevc hardware encoder!");
            }
        }

        if (is_sw_vbr_mode)    //H.264 software encoder
        {
            int is_enable_vbr = 1;
            int video_quality = CalVideoQuality(videoWidth, videoHeight, true);
            int vbr_max_bitrate = CalVbrMaxKBitRate(videoWidth, videoHeight);

            libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);
        }

        libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandeV2());


        if (!is_speex) {
            // set AAC encoder
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);
        } else {
            // set Speex encoder
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);
            libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);
        }

        libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1 : 0);

        libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);

        // libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);

        libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, sw_video_encoder_profile);

        libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);

        // libPublisher.SetRtmpPublishingType(publisherHandle, 0);

        // libPublisher.SmartPublisherSetGopInterval(publisherHandle, 40);

        // libPublisher.SmartPublisherSetFPS(publisherHandle, 15);

        // libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 600, 1200);

        libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);

        if (libPublisher.SmartPublisherSetPostUserDataQueueMaxSize(publisherHandle, 3, 0) != 0) {
            Log.e(TAG, "Failed to SetPostUserDataQueueMaxSize..");
        }
    }

    //设置H.264/H.265硬编码码率(按照25帧计算)
    private int setHardwareEncoderKbps(boolean isH264, int width, int height) {
        int kbit_rate = 2000;
        int area = width * height;

        if (area <= (320 * 300)) {
            kbit_rate = isH264 ? 350 : 280;
        } else if (area <= (370 * 320)) {
            kbit_rate = isH264 ? 470 : 400;
        } else if (area <= (640 * 360)) {
            kbit_rate = isH264 ? 850 : 650;
        } else if (area <= (640 * 480)) {
            kbit_rate = isH264 ? 1000 : 800;
        } else if (area <= (800 * 600)) {
            kbit_rate = isH264 ? 1050 : 950;
        } else if (area <= (900 * 700)) {
            kbit_rate = isH264 ? 1450 : 1100;
        } else if (area <= (1280 * 720)) {
            kbit_rate = isH264 ? 2000 : 1500;
        } else if (area <= (1366 * 768)) {
            kbit_rate = isH264 ? 2200 : 1900;
        } else if (area <= (1600 * 900)) {
            kbit_rate = isH264 ? 2700 : 2300;
        } else if (area <= (1600 * 1050)) {
            kbit_rate = isH264 ? 3000 : 2500;
        } else if (area <= (1920 * 1080)) {
            kbit_rate = isH264 ? 4500 : 2800;
        } else {
            kbit_rate = isH264 ? 4000 : 3000;
        }
        return kbit_rate;
    }

    private int CalVideoQuality(int w, int h, boolean is_h264) {
        int area = w * h;

        int quality = is_h264 ? 23 : 28;

        if (area <= (320 * 240)) {
            quality = is_h264 ? 23 : 27;
        } else if (area <= (640 * 360)) {
            quality = is_h264 ? 25 : 28;
        } else if (area <= (640 * 480)) {
            quality = is_h264 ? 26 : 28;
        } else if (area <= (960 * 600)) {
            quality = is_h264 ? 26 : 28;
        } else if (area <= (1280 * 720)) {
            quality = is_h264 ? 27 : 29;
        } else if (area <= (1600 * 900)) {
            quality = is_h264 ? 28 : 30;
        } else if (area <= (1920 * 1080)) {
            quality = is_h264 ? 29 : 31;
        } else {
            quality = is_h264 ? 30 : 32;
        }

        return quality;
    }

    private int CalVbrMaxKBitRate(int w, int h) {
        int max_kbit_rate = 2000;

        int area = w * h;

        if (area <= (320 * 300)) {
            max_kbit_rate = 320;
        } else if (area <= (360 * 320)) {
            max_kbit_rate = 400;
        } else if (area <= (640 * 360)) {
            max_kbit_rate = 600;
        } else if (area <= (640 * 480)) {
            max_kbit_rate = 700;
        } else if (area <= (800 * 600)) {
            max_kbit_rate = 800;
        } else if (area <= (900 * 700)) {
            max_kbit_rate = 1000;
        } else if (area <= (1280 * 720)) {
            max_kbit_rate = 1400;
        } else if (area <= (1366 * 768)) {
            max_kbit_rate = 1700;
        } else if (area <= (1600 * 900)) {
            max_kbit_rate = 2400;
        } else if (area <= (1600 * 1050)) {
            max_kbit_rate = 2600;
        } else if (area <= (1920 * 1080)) {
            max_kbit_rate = 2900;
        } else {
            max_kbit_rate = 3500;
        }

        return max_kbit_rate;
    }

    public void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }

        Log.i(TAG, "curDegree: " + result);

        camera.setDisplayOrientation(result);
    }
}
